﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Net;

namespace CraigsCrawler
{
    public class Querier
    {
        private const string userAgent = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)";
        private static readonly Lazy<Querier> current = new Lazy<Querier>();
        private static readonly Random random = new Random();

        public static Querier Current
        {
            get { return current.Value; }
        }

        public static TimeSpan QueryInterval { get; set; }

        public static Action Finisher { get; set; }

        private Thread workerThread;
        private ManualResetEvent workerThreadCancel;
        private List<QueueItem> queue = new List<QueueItem>();
        private bool firedFinish = true;

        public Querier()
        {
            workerThread = new Thread(() => this.WorkerThread());
            workerThreadCancel = new ManualResetEvent(false);
            workerThread.Start();
        }

        public void Shutdown()
        {
            MainWindow.WriteLine("Shutting down query thread...");
            workerThreadCancel.Set();
            workerThread.Join();
        }

        public void QueueRequest(string url, object context, Action<string, Stream> successAction, Action<string, Exception> failureAction, string referer = null)
        {
            QueueItem item = new QueueItem() { Url = url, Referer = referer, Context = context, SuccessAction = successAction, FailureAction = failureAction };
            lock (queue)
            {
                queue.Insert(0, item);
            }
        }

        public void RequestImmediately(string url, Action<string, Stream> successAction, Action<string, Exception> failureAction, string referer = null)
        {
            QueueItem item = new QueueItem() { Url = url, Referer = referer, SuccessAction = successAction, FailureAction = failureAction };
            ProcessQueueItem(item);
        }

        public void TailQueueByContext(object context)
        {
            lock (queue)
            {
                List<QueueItem> itemsToTail = queue.Where(i => i.Context == context).ToList();
                foreach (QueueItem item in itemsToTail)
                {
                    queue.Remove(item);
                }
                queue.AddRange(itemsToTail);
            }
        }

        private void WorkerThread()
        {
            while (true)
            {
                Work();

                int milliseconds = (int)QueryInterval.TotalMilliseconds;
                milliseconds = random.Next((int)(milliseconds * 0.8), (int)(milliseconds * 1.2));

                if (workerThreadCancel.WaitOne(milliseconds)) return;
            }
        }

        private void Work()
        {
            try
            {
                if (queue.Count == 0)
                {
                    if (!firedFinish)
                    {
                        firedFinish = true;
                        if (null != Finisher) Finisher();
                    }

                    return;
                }

                firedFinish = false;

                QueueItem item;
                lock (queue)
                {
                    item = queue[0];
                    queue.RemoveAt(0);
                }

                ProcessQueueItem(item);
            }
            catch (Exception e)
            {
                MainWindow.WriteLine(e.ToString());
            }
        }

        private void ProcessQueueItem(QueueItem item)
        {
            HttpWebRequest request = WebRequest.CreateHttp(new Uri(item.Url));
            request.UserAgent = userAgent;
            if (item.Referer != null) request.Referer = item.Referer;

            try
            {
                using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
                {
                    if (response.StatusCode == HttpStatusCode.OK)
                    {
                        item.SuccessAction(item.Url, response.GetResponseStream());
                    }
                    else
                    {
                        item.FailureAction(item.Url, new HttpException(response.StatusCode));
                    }
                }
            }
            catch (Exception e)
            {
                item.FailureAction(item.Url, e);
            }
        }

        private class QueueItem
        {
            public string Url;
            public string Referer;
            public object Context;
            public Action<string, Stream> SuccessAction;
            public Action<string, Exception> FailureAction;
        }

        public class HttpException : Exception
        {
            public HttpException(HttpStatusCode code)
                : base()
            {
                this.Code = code;
            }

            public HttpStatusCode Code { get; set; }

            public override string Message
            {
                get
                {
                    return Code.ToString();
                }
            }
        }
    }
}
